#include <time.h>
#include <pthread.h>

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>


#include "mytimer.h"
#include "configure.h"

#if USE_SSL_AUTHENTICATION
#include "openssl/bio.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#else
#include "virtual_ssl.h"
#endif

#ifndef SERVER_H
#define SERVER_H

extern int debug;

enum DEBUG_LEVEL
{
	DEBUG_STDOUT,
	DEBUG_STDERR,
	DEBUG_MAIN,
	DEBUG_DATE,
	DEBUG_BOTH
};

extern 
int LogInit ();

extern 
int MLog  (const char *c, ...);

extern 
int DLog  (const char *c, ...);

extern 
int Vdebug (enum DEBUG_LEVEL level, const char *c, ...);


typedef enum {
    ACCT_START,
    ACCT_STOP,
    ACCT_ALIVE
} ACCT_TYPE;

typedef struct udp_query_queue {
    struct udp_query_queue *next;
    char *buf;
	int num;
    struct sockaddr_storage From;
    unsigned int FromLen;
    int packet_len;
} udp_query_queue;

typedef unsigned int TABLE_ID;

typedef enum status
{
    USER_AUTHENTICATING,
    USER_ACCOUNTING,
    USER_EXIT,
	USER_KICKOFF
} STATUS;

typedef enum table_type
{
    TABLE_TYPE_FREE,
    TABLE_TYPE_AUTH,
    TABLE_TYPE_ACCT,
    TABLE_TYPE_DEFAULT
} TYPE;

typedef enum packet_type
{
    SERVER_AUTH_REPLY = 0,
    SERVER_AUTH_MESSAGE_REPLY = 1,
    SERVER_AUTH_REJECT = 2,
	CLIENT_AUTH_REQUEST,
	CLIENT_REAUTH_REQUEST,
    CLIENT_KEEPALIVE = 10,
    SERVER_KEEPALIVE,
	SERVER_SETRATE,
	SERVER_KICKOFF,
    CLIENT_EXIT,
    SERVER_EXIT,
    CLIENT_QUERY,
    SERVER_QUERY,
	ADMIN_AUTH_REQUEST = 30,
	ADMIN_AUTH_ACCEPT,
	ADMIN_AUTH_REJECT,
} PACKET_TYPE;

struct nas_user_table
{
    struct timer *timer;
	time_t login_time;
    time_t update_time;
	unsigned int kick_time;
    STATUS status;
    unsigned int packet_id;
	unsigned int instant_up_rate;
	unsigned int instant_down_rate;
	unsigned int total_up_traffic;
	unsigned int total_down_traffic;
	unsigned int last_reply_length;
	unsigned int set_upload_rate;
	unsigned int set_download_rate;
	unsigned int upload_rate_limit;
	unsigned int download_rate_limit;
	unsigned int set_message_length;
	int count;
	int relogin_times;
	char set_message[UDP_PACKET_MAX_MESSAGE_LENGTH];
    char ipaddr[40];
    char name[USER_NAME_LENGTH + 1];
	char pass[USER_PASS_LENGTH + 1];
    char session_tx[SESSION_TX_LENGTH + 1];
    char secret[UDP_PACKET_SECRET_KEY_LENGTH];
    char last_reply[UDP_PACKET_MAX_LENGTH];
    char last_md5[MD5_LENGTH];
	int login_type;
	int user_type;
	char version[10];
};

struct table_chain
{
    struct table_chain *prev;
    struct table_chain *next;
    struct nas_user_table *table;
    TABLE_ID id;
    TYPE type;  // 0 = free, 1 = auth_table, 2 = acct_table
};

/* packet content between server and client is defined here */
# pragma pack (1)

struct udp_packet_keepalive_client
{
	char type;
	unsigned int packet_id;
	unsigned int table_id;
	unsigned int instant_up_rate;
	unsigned int instant_down_rate;
	unsigned int total_up_traffic;
	unsigned int total_down_traffic;
};

struct udp_packet_keepalive_server
{
	char type;
	int packet_id;
	unsigned int up_rate;
	unsigned int down_rate;
	short msg_length;
};

struct login_reply_server 
{
	char type;
	unsigned int table_id;
	char secret[UDP_PACKET_SECRET_KEY_LENGTH];
	char user_type;
	unsigned int up_rate;
	unsigned int down_rate;
	unsigned short msg_length;
};

struct login_request_client
{
	int version;
	char username[USER_NAME_LENGTH];
	char password[USER_PASS_LENGTH];
	char mac[USER_MAC_LENGTH];
	int type;
};
struct login_request_client_200
{
	int version;
	char username[USER_NAME_LENGTH];
	char password[USER_PASS_LENGTH];
	char mac[USER_MAC_LENGTH];
	int type;
	char secret[20];   // used for relogin
	int radversion;
};

# pragma pack ()

//void inet_ntop(sa_family_t, struct sockaddr_storage*, char *, int);
#define print_ipaddr(ipaddr, From) \
        getnameinfo((struct sockaddr *)&(From), \
                    (From).ss_family == PF_INET ? sizeof(struct sockaddr_in): sizeof(struct sockaddr_in6), \
                    ipaddr, 40, NULL, 0, NI_NUMERICHOST);\
        ipaddr[39] = '\0';

#define MAX(a, b) (a) > (b) ? (a) : (b)
#define MIN(a, b) (a) < (b) ? (a) : (b)

#define COUNT_START(a) struct timeval a;\
			gettimeofday(&a,NULL);
#define COUNT_STOP(a) struct timeval s_##a;\
			gettimeofday(&s_##a,NULL);
#define PRINT_TIME(a) float timeuse;\
 			timeuse=1000000*(s_##a.tv_sec-a.tv_sec)+\
       			 s_##a.tv_usec-a.tv_usec;\
       			 timeuse/=1000000;\
     			   printf("Used Time:%f\n",timeuse);

void fr_md5_calc(unsigned char *output, const unsigned char *input,
                 unsigned int inlen);
int copy_message(char *dst, int dstlen, char *src, int id);

int admin_radius(char *reply);
int rad_send_request(const char *name, const char *pass,struct table_chain *chain, char *RadReply, USER_TYPE *type);
int rad_account_packet_send(struct table_chain *table, ACCT_TYPE acct_type, const char *reason, void *rad_reply);
int rad_init() ;
int rad_test() ;
int table_chain_init();
struct table_chain *find_free_table();
int add_table (struct table_chain *table, TYPE type);
int del_table (struct table_chain *table);
int free_table  (struct table_chain *table);


void udp_packet_process() ;
struct table_chain
*new_table_init(char *name, char *pass, char *ipaddr, int type, char *v);
int user_timeout_callback(struct table_chain *table);
int user_start_account(struct table_chain *chain);
int ssl_accept_user(SSL *ssl, char *name, char *pass, char *mac, int *version, int *login_type,
						struct login_request_client_200 *client);
int auth_user_check(char *namet);
struct table_chain *acct_user_check(char *name, char *secret);
int verify_packet(char *packet_buffer, int packet_length,
                  unsigned int *packet_id, unsigned int *table_id, PACKET_TYPE *type);
int parse_client_packet(char *packet_buffer, int packet_length, struct udp_packet_keepalive_client ** client);
int generate_server_reply(PACKET_TYPE type, unsigned int id, char *reply, int *reply_length);
int ssl_send_server_reply(SSL *ssl, struct login_reply_server *server, const char *reply_msg);
//SSL_CTX * ssl_init();
struct aes_ctx * aes_init();
char *get_rad_var(const char *name, const char *buf, size_t buf_len);

int admin_acct(char *reply) ;
int admin_auth(char *reply) ;
int admin_checkuser(char *reply, char *receive);
int admin_kickuser(char *reply, char *receive);
int admin_sendmessage(char *reply, char *receive);
int admin_setrate(char *reply, char *receive);

int close (int socket);
int open_socket(int family, char *port, int socktype);


#if USE_SSL_AUTHENTICATION
static pthread_mutex_t *lock_cs;
static long *lock_count;
#endif

extern struct nas_user_table *table_header;
extern struct table_chain *free_table_header;
extern struct table_chain *free_table_tail;
extern struct table_chain *auth_table_header;
extern struct table_chain *acct_table_header;
extern struct table_chain table_chain[MAX_TABLE_NUMBER];
extern pthread_mutex_t tablemutex;
extern timer_queue queue;
extern int count;
extern int Serv_UDP_Sock;
extern udp_query_queue  *head;
extern udp_query_queue  *tail;
extern unsigned int queue_number;
extern pthread_mutex_t udp_mutex;
extern pthread_cond_t udp_cond;
#if USE_AES_AUTHENTICATION
extern struct aes_ctx aes;
#endif

#endif //SERVER_H
